package glog

import (
	"context"
	"errors"
	"fmt"
	"time"

	"gitee.com/mkwchecking/sr-core/log/ilog"
	"gitee.com/mkwchecking/sr-core/utils/stack"
	"gorm.io/gorm/logger"
)

var _ logger.Interface = (*gormLogger)(nil)

func New(l ilog.Logger, cfg *Config) logger.Interface {
	log := &gormLogger{
		l:        l,
		cfg:      cfg,
		loglevel: ilog.ParseLevel(cfg.Level),
		slow:     cfg.SlowThreshold * time.Millisecond,
	}
	if cfg.Colorful {
		log.mention = newColorfulMentionFormat()
	} else {
		log.mention = newMentionFormat()
	}
	return log
}

type gormLogger struct {
	loglevel ilog.LevelValue
	l        ilog.Logger
	cfg      *Config
	mention  mentionFormat
	slow     time.Duration
}

func (g *gormLogger) getTraceKey() string {
	if g.cfg.TraceKey == "" {
		return "X-Request-Id"
	}
	return g.cfg.TraceKey
}

func (g *gormLogger) wrapTrace(ctx context.Context, args ...interface{}) []interface{} {
	traceKey := g.getTraceKey()
	traceId := ctx.Value(traceKey)
	if traceId == nil {
		return args
	}

	return append(args, traceKey, traceId)
}

// Error implements logger.Interface.
func (g *gormLogger) Error(ctx context.Context, msg string, args ...interface{}) {
	if g.loglevel.Enable(ilog.ErrorLevelValue) {
		g.l.Error(msg, g.wrapTrace(ctx, args)...)
	}
}

// Info implements logger.Interface.
func (g *gormLogger) Info(ctx context.Context, msg string, args ...interface{}) {
	if g.loglevel.Enable(ilog.InfoLevelValue) {
		g.l.Info(msg, g.wrapTrace(ctx, args)...)
	}
}

// LogMode implements logger.Interface.
func (g *gormLogger) LogMode(l logger.LogLevel) logger.Interface {
	nlogger := *g
	switch l {
	case logger.Silent:
		nlogger.loglevel = ilog.PanicLevelValue
	case logger.Error:
		nlogger.loglevel = ilog.ErrorLevelValue
	case logger.Warn:
		nlogger.loglevel = ilog.WarnLevelValue
	case logger.Info:
		nlogger.loglevel = ilog.InfoLevelValue
	default:
		nlogger.loglevel = ilog.DebugLevelValue
	}
	return &nlogger
}

// Warn implements logger.Interface.
func (g *gormLogger) Warn(context.Context, string, ...interface{}) {
	if g.loglevel.Enable(ilog.WarnLevelValue) {
		g.l.Warn("gorm log warn", nil)
	}
}

// Trace implements logger.Interface.
func (g *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
	if g.loglevel.Enable(ilog.TraceLevelValue) {
		return
	}

	l := g.l.With(g.wrapTrace(ctx)...)
	elapsed := time.Since(begin)
	switch {
	case err != nil && g.loglevel.Enable(ilog.ErrorLevelValue):
		sql, rows := fc()
		l.Error(g.mention.traceErrFormat(stack.FileWithLineNum(g.cfg.CallSkip), err, elapsed.Nanoseconds(), rows, sql))
	case elapsed > g.slow && g.slow > 0 && g.loglevel.Enable(ilog.WarnLevelValue):
		sql, rows := fc()
		slowMention := fmt.Sprintf("SLOW SQL >= %v", g.slow)
		l.Warn(g.mention.traceWarnFormat(stack.FileWithLineNum(g.cfg.CallSkip), errors.New(slowMention), elapsed.Nanoseconds(), rows, sql))
	case g.loglevel.Enable(ilog.InfoLevelValue):
		sql, rows := fc()
		l.Info(g.mention.traceFormat(stack.FileWithLineNum(g.cfg.CallSkip), elapsed.Nanoseconds(), rows, sql))
	}

}
